using System;

namespace CubeTimer.Scramble
{
  public class Scramble4x4 : Scramble
  {
    private static readonly String[,] UandD = new String[3, 4];
    private static readonly String[,] FandB = new String[3, 4];
    private static readonly String[,] LandR = new String[3, 4];
    private int m_deadArray, m_oldArray, m_oldGroup, m_previousArray,
                m_previousGroup, m_previousFace, m_currentArray, m_currentGroup, m_currentFace;
    private String m_formatedMove;

    readonly int m_numMoves = 40;

    public Scramble4x4(int numMoves)
    {
      UandD[0, 0] = "U";
      UandD[0, 1] = "u";
      UandD[0, 2] = "d'";
      UandD[0, 3] = "D'";
      UandD[1, 0] = "U'";
      UandD[1, 1] = "u'";
      UandD[1, 2] = "d";
      UandD[1, 3] = "D";
      UandD[2, 0] = "U2";
      UandD[2, 1] = "u2";
      UandD[2, 2] = "d2";
      UandD[2, 3] = "D2";

      FandB[0, 0] = "F";
      FandB[0, 1] = "f";
      FandB[0, 2] = "b";
      FandB[0, 3] = "B'";
      FandB[1, 0] = "F'";
      FandB[1, 1] = "f'";
      FandB[1, 2] = "b";
      FandB[1, 3] = "B";
      FandB[2, 0] = "F2";
      FandB[2, 1] = "f2";
      FandB[2, 2] = "b2";
      FandB[2, 3] = "B2";

      LandR[0, 0] = "L";
      LandR[0, 1] = "l";
      LandR[0, 2] = "r'";
      LandR[0, 3] = "R'";
      LandR[1, 0] = "L'";
      LandR[1, 1] = "l'";
      LandR[1, 2] = "r";
      LandR[1, 3] = "R";
      LandR[2, 0] = "L2";
      LandR[2, 1] = "l2";
      LandR[2, 2] = "r2";
      LandR[2, 3] = "R2";

      m_numMoves = numMoves;
    }

    public override string Name
    {
      get { return "3x3x3"; }
    }

    private void GenerateMove()
    {
      m_currentArray = Math.Abs(m_generator.Next()) % 3;
      m_currentGroup = Math.Abs(m_generator.Next()) % 3;
      m_currentFace = Math.Abs(m_generator.Next()) % 4;

      String[,] arrayChoice = null;
      switch (m_currentArray)
      {
        case 0: arrayChoice = UandD; break;
        case 1: arrayChoice = FandB; break;
        case 2: arrayChoice = LandR; break;
      }

      if (arrayChoice != null)
        m_formatedMove = arrayChoice[m_currentGroup, m_currentFace];
    }

    public override string GenerateScramble()
    {
      GenerateMove();
      string scramble = m_formatedMove;

      m_previousArray = m_currentArray;
      m_previousGroup = m_currentGroup;
      m_previousFace = m_currentFace;

      do GenerateMove();
      while (IsSameFace(m_currentArray, m_currentFace, m_previousArray, m_previousFace));

      scramble += " " + m_formatedMove;

      m_oldArray = m_previousArray;
      m_oldGroup = m_previousGroup;
      m_previousArray = m_currentArray;
      m_previousGroup = m_currentGroup;
      m_previousFace = m_currentFace;

      GenerateMove();

      //If the first two moves are parallel
      if (IsParallel(m_previousArray, m_oldArray))
      {
        //If the first two moves are in the same direction
        if (MovesSameDirection(m_previousArray, m_previousGroup, m_oldArray, m_oldGroup))
        {
          //loop until the next move is an intersecting face
          while (IsParallel(m_currentArray, m_previousArray))
            GenerateMove();
        }
        else
        {
          //loop until the next move is not in the same group as either of the first two moves AND is not the same face
          while ((MovesSameDirection(m_currentArray, m_currentGroup, m_previousArray, m_previousGroup) ||
                  MovesSameDirection(m_currentArray, m_currentGroup, m_oldArray, m_oldGroup)) ||
                 IsSameFace(m_currentArray, m_currentFace, m_previousArray, m_previousFace))
            GenerateMove();
        }
      }
      else
      {
        //loop until the next move is not the same face as the previous
        while (IsSameFace(m_currentArray, m_currentFace, m_previousArray, m_previousFace))
          GenerateMove();
      }

      //add third move to scramble
      scramble += " " + m_formatedMove;

      m_deadArray = m_oldArray;

      m_oldArray = m_previousArray;
      m_oldGroup = m_previousGroup;

      m_previousArray = m_currentArray;
      m_previousGroup = m_currentGroup;
      m_previousFace = m_currentFace;

      //we have three moves of the scramble. Now we need to generate the rest.
      for (int i = 0; i < m_numMoves - 3; i++)
      {
        GenerateMove();

        //if three moves before this one are all parellel, then make this move on an intersecting face
        if (IsParallel(m_previousArray, m_oldArray) && IsParallel(m_previousArray, m_deadArray))
        {
          while (IsParallel(m_currentArray, m_previousArray))
            GenerateMove();
        }
        else
        {
          //If the first two moves are parallel
          if (IsParallel(m_previousArray, m_oldArray))
          {
            //If the first two moves are in the same direction
            if (MovesSameDirection(m_previousArray, m_previousGroup, m_oldArray, m_oldGroup))
            {
              //loop until the next move is an intersecting face
              while (IsParallel(m_currentArray, m_previousArray))
                GenerateMove();
            }
            else
              //loop until the next move is not in the same group as either of the first two moves AND is not the same face
              while (MovesSameDirection(m_currentArray, m_currentGroup, m_previousArray, m_previousGroup) || MovesSameDirection(m_currentArray, m_currentGroup, m_oldArray, m_oldGroup) || IsSameFace(m_currentArray, m_currentFace, m_previousArray, m_previousFace))
              {
                GenerateMove();
              }
          }
          else
          {
            //loop until the next move is not the same face as the previous
            while (IsSameFace(m_currentArray, m_currentFace, m_previousArray, m_previousFace))
              GenerateMove();
          }
        }

        scramble += " " + m_formatedMove;

        m_deadArray = m_oldArray;

        m_oldArray = m_previousArray;
        m_oldGroup = m_previousGroup;

        m_previousArray = m_currentArray;
        m_previousGroup = m_currentGroup;
        m_previousFace = m_currentFace;
      }

      return scramble;
    }

    private static bool IsSameFace(int thisArray, int thisFace, int thatArray, int thatFace)
    {
      return ((thisArray == thatArray) && (thisFace == thatFace));
    }

    private static bool IsParallel(int thisArray, int thatArray)
    {
      return (thisArray == thatArray);
    }

    private static bool MovesSameDirection(int thisArray, int thisGroup, int thatArray, int thatGroup)
    {
      return (((thisArray == thatArray) && (thisGroup == thatGroup)) ||
              ((thisArray == thatArray) && (thisGroup == 2)) ||
              ((thisArray == thatArray) && (thatGroup == 2)));
    }
  }
}